﻿using AiMoBridge.Paths;
using AiMoWindows.CodeScripts;
using AiMoWindows.CodeScripts.IO;
using AiMoWindows.Compiler;
using AiMoWindows.Entrance;
using AiMoWindows.Localization;
using AiMoWindows.MainActions.Mode;
using System.IO;
using System.Text;

namespace AiMoWindows.MainActions.LibraryActions
{
    class ScriptsLibraryItem
    {
        public string CommandEnglishName { get; private set; } = "";
        string m_CompileHash = "";
        public ScriptSource Script { get; }
        public string Name { get; private set; }

        readonly FileInfo scriptFile;
        readonly FileInfo compiledDllInfo;
        readonly FileInfo compiledPdbInfo;
        readonly FileInfo compiledLogInfo;

        const string CompiledDllExtension = ".dll";
        const string CompiledLogExtension = ".log";
        const string CompiledPdbExtension = ".pdb";
        ScriptsLibraryItem(ScriptSource script, FileInfo scriptFile)
        {
            Script = script;
            this.scriptFile = scriptFile;
            Name = Path.GetFileNameWithoutExtension(scriptFile.Name);
            compiledDllInfo = new FileInfo(Path.ChangeExtension(scriptFile.FullName, CompiledDllExtension));
            compiledPdbInfo = new FileInfo(Path.ChangeExtension(scriptFile.FullName, CompiledPdbExtension));
            compiledLogInfo = new FileInfo(Path.ChangeExtension(scriptFile.FullName, CompiledLogExtension));
            GetLog();
        }

        public static ScriptsLibraryItem? BuildCommandItem(FileInfo fileInfo, out string error)
        {
            if (!fileInfo.Exists || fileInfo.Extension != CodeIO.Extension)
            {
                error = string.Format(Local.Get("Script path {0} is invalid"), fileInfo.FullName);
                return null;
            }
            byte[] data;
            // 默认还是从Rhino这里读取，因为曾经没有其他模式
            ScriptSource source = new ScriptSource(ScriptComplileMode.RhinoScript, null, true);
            try
            {
                data = File.ReadAllBytes(fileInfo.FullName);
                if (!CodeIO.FromBytes(data, source, out error))
                {
                    return null;
                }
            }
            catch (Exception ex)
            {
                error = string.Format(Local.Get("Cannot read Script from path {0}{1}{2}"), fileInfo.Name, Environment.NewLine, ex);
                return null;
            }
            if (source.IsEmpty_Main || source.IsEmpty)
            {
                error = Local.Get("Empty scripts");
                return null;
            }
            if (source.ScriptsMode == ScriptComplileMode.ExternalAssemblies)
            {
                error = Local.Get("Cannot add none external assembly aimo scripts to library. (No CSharpScriptInstance class)");
                return null;
            }
            return new ScriptsLibraryItem(source, fileInfo);
        }
        public bool ReloadScript(out string error)
        {
            try
            {
                byte[] data = File.ReadAllBytes(scriptFile.FullName);
                if (!CodeIO.FromBytes(data, Script, out error))
                {
                    return false;
                }
            }
            catch (Exception ex)
            {
                error = string.Format(Local.Get("Cannot read Script from path {0}{1}{2}"), scriptFile.FullName, Environment.NewLine, ex);
                return false;
            }
            if (Script.IsEmpty)
            {
                error = Local.Get("Empty scripts");
                return false;
            }

            return true;
        }

        public bool SendToEdit(out string error)
        {
            return AiMoProgram.Instance.OpenNewAimoScriptForm(scriptFile.FullName, out error);
        }

        public bool GetRunningDll(out string pathToRun, out string error)
        {

            if (!compiledDllInfo.Exists)
            {
                pathToRun = "";
                error = "Error, no compiled file existed";
                return false;
            }
            try
            {
                PathManager.GetTempDllPath(out _, out pathToRun, out string pdbPath);
                File.Copy(compiledDllInfo.FullName, pathToRun);
                if (compiledPdbInfo.Exists) File.Copy(compiledPdbInfo.FullName, pdbPath);
                error = "";
                return true;
            }
            catch (Exception ex)
            {
                pathToRun = "";
                error = $"Cannot get running dll at {compiledDllInfo.FullName}, {ex.Message}";
                return false;
            }

        }
        public bool Delete(out string error)
        {
            if (CheckReadonly(out error))
            {
                return false;
            }
            try
            {
                compiledDllInfo.Delete();
                compiledPdbInfo.Delete();
                compiledLogInfo.Delete();
                scriptFile.Delete();
                return true;
            }
            catch (Exception ex)
            {
                error = string.Format(Local.Get("Cannot delete {0}{1}{2}"), Name, Environment.NewLine, ex);
                return false;
            }
        }

        bool CheckReadonly(out string error)
        {
            scriptFile.Refresh();
            compiledDllInfo.Refresh();
            compiledPdbInfo.Refresh();
            compiledLogInfo.Refresh();
            if (scriptFile.IsReadOnly || compiledDllInfo.IsReadOnly || compiledLogInfo.IsReadOnly)
            {
                string occupiedFile =
                    (scriptFile.IsReadOnly ? scriptFile.Name + ", " : "") +
                    (compiledDllInfo.IsReadOnly ? compiledDllInfo.Name + ", " : "") +
                    (compiledPdbInfo.IsReadOnly ? compiledPdbInfo.Name + ", " : "") +
                    (compiledLogInfo.IsReadOnly ? compiledLogInfo.Name : "");
                error = string.Format(Local.Get("File {0} is occupied, please restart AiMo than try again."), occupiedFile);
                return true;
            }
            error = "";
            return false;
        }
        static readonly char[] invalidChars = Path.GetInvalidFileNameChars().Append('.').ToArray();
        public bool Rename(string newName, out string error)
        {
            try
            {
                char[] chars = newName.ToCharArray();
                foreach (char invalidChar in invalidChars)
                {
                    if (chars.Contains(invalidChar))
                    {
                        error = string.Format(Local.Get("Cannot change name {0} to {1}, '{2}' should not exist"), Name, newName, invalidChar);
                        return false;
                    }
                }
                string directoryName = scriptFile.DirectoryName ?? throw new Exception($"Directory of scripFile {scriptFile} cannot be null");
                string newScriptName = Path.Combine(directoryName, newName + CodeIO.Extension);
                string newDllPath = Path.Combine(directoryName, newName + CompiledDllExtension);
                string newPdbPath = Path.Combine(directoryName, newName + CompiledPdbExtension);
                string newLogPath = Path.Combine(directoryName, newName + CompiledLogExtension);

                if (CheckReadonly(out error))
                {
                    return false;
                }

                scriptFile.MoveTo(newScriptName);
                compiledLogInfo.MoveTo(newLogPath);
                compiledPdbInfo.MoveTo(newPdbPath);
                compiledDllInfo.MoveTo(newDllPath);
                error = "";
                Name = newName;
                return true;
            }
            catch (Exception ex)
            {
                error = string.Format(Local.Get("Cannot rename the file {0} to {1}{2}{3}"), Name, newName, Environment.NewLine, ex);
                return false;
            }
        }

        public bool ChangeCommandName(string name, out string error)
        {
            string preName = CommandEnglishName;
            CommandEnglishName = name;
            if (!WriteLogText(out error))
            {
                CommandEnglishName = preName;
                return false;
            }
            return true;
        }

        void GetLog()
        {
            if (compiledLogInfo.Exists)
            {
                string[] lines = File.ReadAllLines(compiledLogInfo.FullName);
                if (lines.Length > 1)
                {
                    m_CompileHash = lines[0].Trim();
                    CommandEnglishName = lines[1].Trim();
                }
            }
        }
        bool WriteLogText(out string error)
        {
            try
            {
                File.WriteAllLines(compiledLogInfo.FullName, new string[] { m_CompileHash, CommandEnglishName }, Encoding.UTF8);

                error = "";
                return true;
            }
            catch (Exception ex)
            {
                error = "Cannot write log " + ex.Message;
                return false;
            }

        }

        public bool Compile(RhinoScriptsCompiler compiler, StringBuilder compileErrors)
        {
            try
            {
                string hash = Script.GetScriptHash();
                GetLog();
                if (m_CompileHash == hash)
                {
                    //已经编译过了
                    return true;
                }
                m_CompileHash = hash;
                CompiledResult resultPath = compiler.CompileScriptInstance(Script, compileErrors);
                if (resultPath.IsUnset)
                {
                    return false;
                }
                File.Copy(resultPath.DllPath, compiledDllInfo.FullName, true);
                File.Copy(resultPath.PdbPath, compiledPdbInfo.FullName, true);
                if (!WriteLogText(out string errorLog))
                {
                    compileErrors.AppendLine(errorLog);
                }
                return true;
            }
            catch (Exception ex)
            {
                compileErrors.AppendLine(Local.Get("Cannot complie script:") + ex.Message);
                return false;
            }
        }

    }
}
